import pygame

from . import powerup
from .. import tools, setup
from .. import constants as C
import json
import os


# 游戏角色类(继承自精灵类)
class Player(pygame.sprite.Sprite):
    def __init__(self, name):
        pygame.sprite.Sprite.__init__(self)
        self.name = name  # 记录角色名
        self.load_data()  # 载入角色数据(从角色json文件中读取)
        self.setup_states()  # 主角所处状态(角色朝向/存活/无敌/发射火球等)设定方法
        self.setup_velocities()  # 速度(前进速度/加速度等)设定方法
        self.setup_timers()  # 计时器(设定一系列计时器:角色位移时间/变身时间等)设定方法
        self.load_images()  # 图片(各种帧对应的造型图片)载入方法

    # 角色数据载入方法
    def load_data(self):
        file_name = self.name + '.json'  # 通过字符串拼接得到目标角色.json文件名
        file_path = os.path.join('source/data/player', file_name)  # 拼接得到文件路径
        with open(file_path) as f:
            self.player_data = json.load(f)

    # 角色所处状态设定方法
    def setup_states(self):
        self.state = 'stand'  # 角色状态机(默认为行走)
        self.face_right = True  # 是否面朝右
        self.dead = False  # 是否死亡
        self.big = False  # 是否变大
        self.fire = False  # 是否能发射火球
        self.can_jump = True  # 是否能跳跃(按下跳跃键后只跳跃一次，防止按住后连续跳跃)
        self.can_shoot = True  # 是否能发射子弹(防止无限发射)
        self.hurt_immune = False  # 是否伤害免疫(默认为False,变身过程会用到)

    # 速度设定方法
    def setup_velocities(self):
        speed = self.player_data['speed']
        self.x_vel = 0  # x方向速度(默认均为零)
        self.y_vel = 0  # y方向速度
        # vel:velocity速度;accel:accelorate加速;turn:turnover转身
        self.max_walk_vel = speed['max_walk_speed']
        self.max_run_vel = speed['max_run_speed']
        self.max_y_vel = speed['max_y_velocity']
        self.jump_vel = speed['jump_velocity']
        self.walk_accel = speed['walk_accel']
        self.run_accel = speed['run_accel']
        self.turn_accel = speed['turn_accel']
        self.gravity = C.GRAVITY  # 重力
        self.anti_gravity = C.ANTI_GRAVITY  # 反重力

        self.max_x_vel = self.max_walk_vel
        self.x_accel = self.walk_accel

    # 计时器设定方法
    def setup_timers(self):
        self.walking_timer = 0  # 前进时长
        self.transition_timer = 0  # 变身时长
        self.death_timer = 0  # 用以记录死亡时间
        self.hurt_immune_timer = 0  # 用以记录伤害免疫的时间
        self.last_fireball_timer = 0  # 用以记录上次火球发射时间(防止发射太快)

    # 角色图片载入方法
    def load_images(self):
        sheet = setup.GRAPHICS['mario_bros']
        # 用列表存放角色数据内的image_frames键对应的值(所有帧图片信息)
        frame_rects = self.player_data['image_frames']

        self.right_small_normal_frames = []  # 右向小普通 帧图片列表
        self.right_big_normal_frames = []  # 右向大普通 帧图片列表
        self.right_big_fire_frames = []  # 右向大火 帧图片列表
        self.left_small_normal_frames = []  # 左向小普通 帧图片列表
        self.left_big_normal_frames = []  # 左向大普通 帧图片列表
        self.left_big_fire_frames = []  # 左向大火 帧图片列表

        # 小普通帧图片列表[右向, 左向]
        self.small_normal_frames = [self.right_small_normal_frames, self.left_small_normal_frames]
        # 大普通帧图片列表[右向, 左向]
        self.big_normal_frames = [self.right_big_normal_frames, self.left_big_normal_frames]
        # 大火帧图片列表[右向, 左向]
        self.big_fire_frames = [self.right_big_fire_frames, self.left_big_fire_frames]
        # 总帧图片列表(存放所有种类帧图片)
        self.all_frames = [self.right_small_normal_frames, self.right_big_normal_frames,
                           self.right_big_fire_frames, self.left_small_normal_frames,
                           self.left_big_normal_frames, self.left_big_fire_frames]
        # 默认右向左向帧图片列表
        self.right_frames = self.right_small_normal_frames
        self.left_frames = self.left_small_normal_frames

        # 遍历帧图片坐标键值对截取对应图片
        for group, group_frame_rects in frame_rects.items():
            for frame_rect in group_frame_rects:
                # 调用tools.get_image()方法截取对应图片,通过字典访问方法获取每组坐标四个参数
                right_image = tools.get_image(sheet, frame_rect['x'], frame_rect['y'],
                                              frame_rect['width'], frame_rect['height'],
                                              (0, 0, 0), C.PLAYER_MULTI)
                # 通过调用pygame.transform.flip(a, b, c)方法将右向图片反转为左向图片
                # 参数a为要翻转的图片,参数b为左右是否翻转,参数c为上下是否翻转
                left_image = pygame.transform.flip(right_image, True, False)
                # 根据键名将截取的右向左向帧图片存入相应列表
                if group == 'right_small_normal':
                    self.right_small_normal_frames.append(right_image)
                    self.left_small_normal_frames.append(left_image)
                if group == 'right_big_normal':
                    self.right_big_normal_frames.append(right_image)
                    self.left_big_normal_frames.append(left_image)
                if group == 'right_big_fire':
                    self.right_big_fire_frames.append(right_image)
                    self.left_big_fire_frames.append(left_image)

        # 默认:索引为0,帧图片列表为右向帧列表,帧图片为列表首个元素
        self.frame_index = 0
        self.frames = self.right_frames
        self.image = self.frames[self.frame_index]
        self.rect = self.image.get_rect()

    # 游戏角色更新方法(通过更新角色速度及对应帧图片列表,在level类中进行位移计算并更新)
    def update(self, keys, level):
        self.current_time = pygame.time.get_ticks()  # 注册计时器记录系统时间
        self.handle_states(keys, level)  # 调用状态机处理方法实现对角色的操作响应和更新
        self.is_hurt_immune()  # 时刻判断是否开启无敌模式(防止变小后一直处于伤害免疫开启状态)

    def handle_states(self, keys, level):
        # 时刻判断特殊技能是否可用(跳跃、发射子弹等)
        self.can_jump_or_not(keys)  # 时刻判断是否能跳跃
        self.can_shoot_or_not(keys)  # 时刻判断是否能发射子弹
        # 时刻对各种状态机处理及按键响应
        if self.state == 'stand':
            self.stand(keys, level)
        elif self.state == 'walk':
            self.walk(keys, level)
        elif self.state == 'jump':
            self.jump(keys, level)
        elif self.state == 'fall':
            self.fall(keys, level)
        elif self.state == 'die':
            self.die(keys)
        elif self.state == 'small2big':
            self.small2big(keys)
        elif self.state == 'big2small':
            self.big2small(keys)
        elif self.state == 'big2fire':
            self.big2fire(keys)

        if self.face_right:
            self.image = self.right_frames[self.frame_index]
        else:
            self.image = self.left_frames[self.frame_index]

    def stand(self, keys, level):
        self.frame_index = 0
        self.x_vel = 0
        self.y_vel = 0
        if keys[pygame.K_RIGHT]:
            self.face_right = True
            self.state = 'walk'
        elif keys[pygame.K_LEFT]:
            self.face_right = False
            self.state = 'walk'
        elif keys[pygame.K_UP] and self.can_jump:
            self.state = 'jump'
            self.y_vel = self.jump_vel
        elif keys[pygame.K_x]:  # x键发射火球
            if self.fire and self.can_shoot:  # 前提条件是可以喷火(喷火形态)
                self.shoot_fireball(level)

    def walk(self, keys, level):
        # 字母z键加速
        if keys[pygame.K_z]:
            self.max_x_vel = self.max_run_vel
            self.x_accel = self.run_accel
        else:
            self.max_x_vel = self.max_walk_vel
            self.x_accel = self.walk_accel
        # x键发射火球
        if keys[pygame.K_x]:
            if self.fire and self.can_shoot:  # 前提条件是可以喷火(喷火形态)
                self.shoot_fireball(level)
        # 跳跃
        if keys[pygame.K_UP] and self.can_jump:
            self.state = 'jump'
            self.y_vel = self.jump_vel

        # 帧图片切换操作;calc_frame_duration:帧持久函数(动态确定帧切换频率:行走和跑步等不同状态的频率是不同的)
        if self.current_time - self.walking_timer > self.calc_frame_duration():
            if self.frame_index < 3:
                self.frame_index += 1
            else:
                self.frame_index = 1
            self.walking_timer = self.current_time
        if keys[pygame.K_RIGHT]:
            self.face_right = True
            if self.x_vel < 0:
                self.frame_index = 5  # 刹车帧
                self.x_accel = self.turn_accel
            self.x_vel = self.calc_vel(self.x_vel, self.x_accel, self.max_x_vel, True)

        elif keys[pygame.K_LEFT]:
            self.face_right = False
            if self.x_vel > 0:
                self.frame_index = 5
                self.x_accel = self.turn_accel
            self.x_vel = self.calc_vel(self.x_vel, self.x_accel, self.max_x_vel, False)
        else:
            if self.face_right:
                self.x_vel -= self.x_accel
                if self.x_vel < 0:
                    self.x_vel = 0
                    self.state = 'stand'
            else:
                self.x_vel += self.x_accel
                if self.x_vel > 0:
                    self.x_vel = 0
                    self.state = 'stand'

    # 判断是否能跳
    def can_jump_or_not(self, keys):
        if not keys[pygame.K_UP]:
            self.can_jump = True

    def jump(self, keys, level):
        self.frame_index = 4  # 第4帧为跳跃帧图片
        self.y_vel += self.anti_gravity
        self.can_jump = False

        if self.y_vel >= 0:
            self.state = 'fall'

        # 跳跃过程左右移动和攻击(喷火)
        if keys[pygame.K_RIGHT]:
            self.x_vel = self.calc_vel(self.x_vel, self.x_accel, self.max_x_vel, True)
        elif keys[pygame.K_LEFT]:
            self.x_vel = self.calc_vel(self.x_vel, self.x_accel, self.max_x_vel, False)
        if keys[pygame.K_x]:  # x键发射火球
            if self.fire and self.can_shoot:  # 前提条件是可以喷火(喷火形态)
                self.shoot_fireball(level)

        # 小跳实现思路:一旦松开跳跃键立即转为下落状态
        if not keys[pygame.K_UP]:
            self.state = 'fall'

    def fall(self, keys, level):
        self.y_vel = self.calc_vel(self.y_vel, self.gravity, self.max_y_vel)
        # 下落过程左右移动和攻击(喷火)
        if keys[pygame.K_RIGHT]:
            self.x_vel = self.calc_vel(self.x_vel, self.x_accel, self.max_x_vel, True)
        elif keys[pygame.K_LEFT]:
            self.x_vel = self.calc_vel(self.x_vel, self.x_accel, self.max_x_vel, False)
        if keys[pygame.K_x]:  # x键发射火球
            if self.fire and self.can_shoot:  # 前提条件是可以喷火(喷火形态)
                self.shoot_fireball(level)

    def die(self, keys):
        self.rect.y += self.y_vel
        self.y_vel += self.anti_gravity

    def go_die(self):
        self.dead = True  # 设置死亡标志为True
        self.y_vel = self.jump_vel  # y向赋予一个跳跃速度，达到死亡后跳出屏幕的动画效果
        self.frame_index = 6  # 动画帧切换到6(死亡帧图片)
        self.state = 'die'  # 角色状态设置为死亡
        self.death_timer = self.current_time  # 将当前游戏时间赋给死亡时间记录器

    # 判断是否处于无敌状态(防止变身后一直处于伤害免疫状态)
    def is_hurt_immune(self):
        if self.hurt_immune:
            if self.hurt_immune_timer == 0:  # 判断还未开始计时
                self.hurt_immune_timer = self.current_time  # 将当前系统时间赋给无敌状态计时器
                self.blank_image = pygame.Surface((1, 1))  # 设置一个空白帧图片(用以模拟无敌效果动画)
            elif self.current_time - self.hurt_immune_timer < 3000:  # 无敌持续3秒
                # 相当于将每100ms均分成两部分,前50ms显示空白帧，后50ms显示正常帧
                # 相当于达到一个模拟无敌状态角色一直闪动的视觉动画效果(符合原著)
                if (self.current_time - self.hurt_immune_timer) % 100 < 50:
                    self.image = self.blank_image
            else:  # 无敌状态持续超过2s则关闭,并将无敌状态计时器归零
                self.hurt_immune = False
                self.hurt_immune_timer = 0

    # 判断是否能发射子弹
    def can_shoot_or_not(self, keys):
        if not keys[pygame.K_x]:
            self.can_shoot = True  # 判断发射子弹键松开状态才可以再次发射

    # 发射火球方法(1.角色帧切换为扔火球姿态;2.实例化一个火球对象出来)
    def shoot_fireball(self, level):
        if self.current_time - self.last_fireball_timer > 300:  # 发射不能太频繁
            self.frame_index = 6  # 扔火球帧索引
            # 实例化一个火球精灵对象
            fireball = powerup.Fireball(self.rect.centerx, self.rect.centery, self.face_right)
            level.powerup_group.add(fireball)  # 将实例化的火球对象添加到道具组
            self.can_shoot = False  # 设置能发射标志为False(防止无限发射)
            self.last_fireball_timer = self.current_time  # 更新发射计时器

    # 变大过程处理(模拟变大动画)
    def small2big(self, keys):
        frame_dur = 100  # 设定切换帧持续时间为100ms
        sizes = [1, 0, 1, 0, 1, 2, 0, 1, 2, 0, 2]  # 动画帧切换顺序列表:0-小;1-中;2-大;
        # 3种尺寸帧图片对应的帧素材列表和索引:
        # 默认按0:小->1:中->2:大 放置,方便配合动画切换列表的值作为其索引
        frames_and_idx = [(self.small_normal_frames, 0), (self.small_normal_frames, 7), (self.big_normal_frames, 0)]
        if self.transition_timer == 0:  # 检测还未开始变身
            self.big = True  # 修改变大标志为True
            self.transition_timer = self.current_time  # 设置变身计时器为当前系统时间
            self.changing_idx = 0  # 设置动画帧切换列表索引为0(第一项)
        elif self.current_time - self.transition_timer > frame_dur:  # 判断每一帧是否超过设定帧持续时间
            # 如果不到变身动画最后一帧,继续切换动画帧
            # 先根据动画切换的顺序索引从顺序列表取出对应帧尺寸值
            # 再根据帧尺寸值作为索引从帧素材列表取出对应的帧列表和所在列表的位置索引
            frames, idx = frames_and_idx[sizes[self.changing_idx]]
            self.change_player_image(frames, idx)  # 根据取得帧数据调用更新帧图片方法
            self.changing_idx += 1  # 更新动画顺序索引 + 1
            self.transition_timer = self.current_time  # 更新变身计时器为当前系统时间
            if self.changing_idx == len(sizes):  # 判断是否到动画列表最后一项(动画完成)
                self.transition_timer = 0  # 变身计时器归零
                self.state = 'walk'  # 切换状态为行走
                # 设置帧素材为大尺寸形态帧列表
                self.right_frames = self.right_big_normal_frames
                self.left_frames = self.left_big_normal_frames

    # 变小过程处理(模拟变小动画)
    def big2small(self, keys):
        frame_dur = 100  # 设定切换帧持续时间为100ms
        sizes = [2, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0]  # 动画帧切换顺序列表:0-小;1-中;2-大;
        # 3种尺寸帧图片对应的帧素材列表和索引:
        # 默认按0:小->1:中->2:大 放置,方便配合动画切换列表的值作为其索引
        frames_and_idx = [(self.small_normal_frames, 8), (self.big_normal_frames, 8), (self.big_normal_frames, 4)]
        if self.transition_timer == 0:  # 检测还未开始变身
            self.big = False  # 修改变大标志为False
            self.transition_timer = self.current_time  # 设置变身计时器为当前系统时间
            self.changing_idx = 0  # 设置动画帧切换列表索引为0(第一项)
        elif self.current_time - self.transition_timer > frame_dur:  # 判断每一帧是否超过设定帧持续时间
            # 如果不到变身动画最后一帧,继续切换动画帧
            # 先根据动画切换的顺序索引从顺序列表取出对应帧尺寸值
            # 再根据帧尺寸值作为索引从帧素材列表取出对应的帧列表和所在列表的位置索引
            frames, idx = frames_and_idx[sizes[self.changing_idx]]
            self.change_player_image(frames, idx)  # 根据取得帧数据调用更新帧图片方法
            self.changing_idx += 1  # 更新动画顺序索引 + 1
            self.transition_timer = self.current_time  # 更新变身计时器为当前系统时间
            if self.changing_idx == len(sizes):  # 判断是否到动画列表最后一项(动画完成)
                self.transition_timer = 0  # 变身计时器归零
                self.state = 'walk'  # 切换状态为行走
                # 设置帧素材为小尺寸形态帧列表
                self.right_frames = self.right_small_normal_frames
                self.left_frames = self.left_small_normal_frames

    # 变为火焰形态(魔拟由大变为火焰形态动画)
    def big2fire(self, keys):
        frame_dur = 100  # 设定切换帧持续时间为100ms
        sizes = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0]  # 动画帧切换顺序列表:0-小;1-中;2-大;
        # 3种尺寸帧图片对应的帧素材列表和索引:
        # 默认按0:小->1:中->2:大 放置,方便配合动画切换列表的值作为其索引
        frames_and_idx = [(self.big_fire_frames, 3), (self.big_normal_frames, 3)]
        if self.transition_timer == 0:  # 检测还未开始变身
            self.fire = True  # 修改喷火标志为False
            self.transition_timer = self.current_time  # 设置变身计时器为当前系统时间
            self.changing_idx = 0  # 设置动画帧切换列表索引为0(第一项)
        elif self.current_time - self.transition_timer > frame_dur:  # 判断每一帧是否超过设定帧持续时间
            # 如果不到变身动画最后一帧,继续切换动画帧
            # 先根据动画切换的顺序索引从顺序列表取出对应帧尺寸值
            # 再根据帧尺寸值作为索引从帧素材列表取出对应的帧列表和所在列表的位置索引
            frames, idx = frames_and_idx[sizes[self.changing_idx]]
            self.change_player_image(frames, idx)  # 根据取得帧数据调用更新帧图片方法
            self.changing_idx += 1  # 更新动画顺序索引 + 1
            self.transition_timer = self.current_time  # 更新变身计时器为当前系统时间
            if self.changing_idx == len(sizes):  # 判断是否到动画列表最后一项(动画完成)
                self.transition_timer = 0  # 变身计时器归零
                self.state = 'walk'  # 切换状态为行走
                # 设置帧素材为小尺寸形态帧列表
                self.right_frames = self.right_big_fire_frames
                self.left_frames = self.left_big_fire_frames

    # 动画帧图片更新方法
    def change_player_image(self, frames, idx):
        self.frame_index = idx  # 接收索引
        # 根据朝向取出不同帧图片列表,并根据索引从列表取出对应帧图片
        if self.face_right:
            self.right_frames = frames[0]
            self.image = self.right_frames[self.frame_index]
        else:
            self.left_frames = frames[1]
            self.image = self.left_frames[self.frame_index]
        # 记录上一帧的图片底部和x中心坐标(作为下一帧图片位置的参考,防止变大后角色底部跑到地面以下)
        last_frame_bottom = self.rect.bottom
        last_frame_centerx = self.rect.centerx
        # 根据新的帧图片素材和参考坐标更新动画帧图片
        self.rect = self.image.get_rect()
        self.rect.bottom = last_frame_bottom
        self.rect.centerx = last_frame_centerx

    # 速度计算函数
    def calc_vel(self, vel, accel, max_vel, is_positive=True):
        if is_positive:
            return min(vel + accel, max_vel)
        else:
            return max(vel - accel, -max_vel)

    # 帧持久函数(动态确定帧切换频率:行走和跑步等不同状态的频率是不同的)
    def calc_frame_duration(self):
        duration = -60 / self.max_run_vel + abs(self.x_vel) + 80
        return duration


'''
        if keys[pygame.K_RIGHT]:  # 键盘右键
            self.state = 'walk'  # 角色状态机更新为行走
            self.x_vel = 5  # x向速度变为5
            self.y_vel = 0  # y向速度为0
            self.frames = self.right_frames  # 帧图片列表变为右向帧图片列表
        if keys[pygame.K_LEFT]:  # 键盘左键
            self.state = 'walk'  # 角色状态更新为行走
            self.x_vel = -5  # x向速度变为-5
            self.y_vel = 0  # y向速度为0
            self.frames = self.left_frames  # 帧图片列表变为左向帧图片列表
        if keys[pygame.K_SPACE]:  # 键盘空格键
            self.state = 'jump'  # 状态机更新为跳跃
            self.y_vel = -5  # y向速度变为-5(pygame游戏画面向下为y正向)
        if self.state == 'walk':  # 判断状态机为行走状态
            # 根据角色前进计时器和当前时间比较判断是否更换帧图片
            if self.current_time - self.walking_timer > 100:
                self.walking_timer = self.current_time  # 达到更换间隔则更新角色前进时间为当前系统时间
                self.frame_index += 1  # 帧图片列表索引+1
                self.frame_index %= 4  # 索引对4取余,防止越界且达到循环效果
        if self.state == 'jump':  # 判断状态机为跳跃状态
            self.frame_index = 4  # 帧图片列表索引变为4

        self.image = self.frames[self.frame_index]  # 根据新帧列表和新索引更新当前角色图片
'''
